#include "ComputationDisk.h"

using namespace std;

ComputationDisk::ComputationDisk(){
  _env = new Environment(); 
  testFileExists(NAME1); 
  testFileExists(NAME2); 
}

ComputationDisk::ComputationDisk(Environment * env){
  _env = env; 
  testFileExists(NAME1); 
  testFileExists(NAME2); 
}

ComputationDisk::~ComputationDisk(){
  delete _env;
}

void ComputationDisk::setEnvironment(Environment * env) {
  _env = env; 
}

Environment * ComputationDisk::getEnvironment(){
  return _env;
}

void ComputationDisk::threadReadSeq(Environment ** tabEnv){
  int nbThread = _env->getNbThread();

  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkReadSeq, (void*)tabEnv[i]);
  }
  for (int i =0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }  

  for(int i=0; i< nbThread; i++){
    tabEnv[i]->resetResult();
  }
  
}

void ComputationDisk::threadReadRand(Environment ** tabEnv){
  
  int nbThread = _env->getNbThread();
  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkReadRand, (void*)(tabEnv[i]));
  }
  for (int i = 0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }

  for(int i=0; i< nbThread; i++){
    tabEnv[i]->resetResult();
  }
}

void ComputationDisk::threadWriteRand(Environment ** tabEnv){
  int nbThread = _env->getNbThread();
  
  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkWriteRand, (void*)tabEnv[i]);
      }
  for (int i =0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }  

  for(int i=0; i< nbThread; i++){
    tabEnv[i]->resetResult();
  } 
}

void ComputationDisk::threadWriteSeq(Environment ** tabEnv){
  int nbThread = _env->getNbThread();
  
  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkWriteSeq, (void*)tabEnv[i]);
  }
  for (int i =0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }  

  for(int i=0; i< nbThread; i++){
    tabEnv[i]->resetResult();
  }
}

// TO test
void ComputationDisk::testFileExists(const char* name){  

  if (!access(name, R_OK|W_OK)) {
    // the file exists
    // check the size 
    const char * fileType= "r";
    FILE * file = fopen(name, fileType);
    fseek(file, 0, SEEK_END);
    unsigned long size = ftell(file);
    fclose(file);
    if (size == GB){
      cout << "The test file " << name << " exists" << endl ; 
      // if the size is OK, do nothing
      return; 
    }
  }

  const char * fileType= "w+";
  cout << "Creation of " << name << "... " ; 
  fflush(stdout);
  // create the file
  FILE * file = fopen(name, fileType);
  char* content = (char*) malloc(sizeof(char));
  content[0] = 'a';
  
  // write into the file
  for (int i =0; i< GB; i++){
    fwrite(content, 1, 1,file);
  }
  cout << " Completed." << endl; 
  // free memory space
  fclose(file);
  free(content);
}

void ComputationDisk::runTests(){

  //Set the precision of cerr to 15 digits
  cerr << setprecision(15);

  Environment * env2 = new Environment(_env, NAME2);
  Environment ** tabEnv = (Environment**) malloc(2*sizeof(Environment*));
  tabEnv[0] = _env;
  tabEnv[1] = env2;

  switch(_env->getMethod()){
  case 0 : // read and write
    switch (_env->getAccess()){ 
    case 0: // random & Sequential 
      // Read Random
      threadReadRand(tabEnv);
      // Read Sequential
      threadReadSeq(tabEnv);
      // WriteRandom
      threadWriteRand(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      // Write Sequential
      threadWriteSeq(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break; 
    case 1: // Sequential
      // Read Sequential
      threadReadSeq(tabEnv);
      // Write Sequential
      threadWriteSeq(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break;  
    case 2: // random
      // read Random
      threadReadRand(tabEnv);
      // write Random
      threadWriteRand(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break;
    default: 
      cout << "Something went wrong. Please check the arguments."<< endl; 
      break; 
    }
    break; 
  case 1 : // read
    switch (_env->getAccess()){
    case 0: // random & Sequential
      // Read Random
      threadReadRand(tabEnv);
      // Read Sequential
      threadReadSeq(tabEnv);
      break; 
    case 1: // Sequential
      // Read Sequential
      threadReadSeq(tabEnv);
      break;  
    case 2: // random
      // Read Random
      threadReadRand(tabEnv);
      break;
    default: 
      cout << "Something went wrong. Please check the arguments."<< endl; 
      break; 
    }
    break; 
  case 2 : // write
    switch (_env->getAccess()){
    case 0: //random & Sequential
      // WriteRandom
      threadWriteRand(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      // Write Sequential
      threadWriteSeq(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break; 
    case 1: // Sequential
      // Write Sequential
      threadWriteSeq(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break;  
    case 2: // Random
       // WriteRandom
      threadWriteRand(tabEnv);
      testFileExists(NAME1);
      testFileExists(NAME2);
      break;
    default: 
      cout << "Something went wrong. Please check the arguments."<< endl; 
      break; 
    }
    break; 
  default: 
      cout << "Something went wrong. Please check the arguments."<< endl; 
      break; 

  }
}


void * benchMarkReadSeq(void * ptr){
  Environment * env = (Environment*)ptr; 
  const char * type = "r"; 
  const char * name = env->getFilename();
  FILE * f = fopen(name, type);
  int size = env->getBlockSize();
  int  nmemb = GB/size;
  char* tmpChar= (char*) malloc(size*sizeof(char));

  struct timeval tbegin, tend; 
  
  // get begining time in ms
  gettimeofday(&tbegin,NULL);

  //Read one GB, 'size' bytes at a time
  for (int i =0; i<nmemb; i++) {
    fread(tmpChar, 1, size, f);
  }  
  // get ending time in ms
  gettimeofday(&tend,NULL);

  float timesec = (tend.tv_sec - tbegin.tv_sec)*1000000;
  double totalTime = (tend.tv_usec - tbegin.tv_usec)+timesec; 

  cout << "Sequential read : " << endl ; 
  cout << "Total Time : " << totalTime << "MicroSeconds" <<endl ; 
  env->addResult(totalTime);
 
  float throughput = (GB/MB)/(totalTime/1000000);
  float latency = (totalTime/1000) / GB; 
  cout << "Throughput : " << throughput << "MB/s" <<endl; 
  //This output format is used in the script 
  cerr  << throughput << ";" <<endl; 

  //The latency is displayed only if the read was on the minimal size
  if(size == B){
    cout << "Latency : "<< latency << "ms" << endl ; 
    //This output format is used in hte script
    cerr << fixed << ";;;;" << latency  << ";" << endl ; 
  }
  free(tmpChar);
  fclose(f); 

  return NULL;
}

void * benchMarkWriteSeq(void * ptr){
  Environment * env = (Environment*)ptr; 
  const char * type = "w";
  const char * name = env->getFilename();
  FILE * f = fopen(name, type);
  int size = env->getBlockSize();
  int nmemb = GB/size;
  char* tmpChar= (char*) malloc(size*sizeof(char));
  struct timeval tbegin, tend; 
  
  // fill memory to test writing speed
  for (int i=0; i< size; i++){
    tmpChar[i] = 'b';
  }
  
  // get begining time in ms
  gettimeofday(&tbegin,NULL);

  for (int i =0; i<nmemb; i++) {
    fwrite(tmpChar, size, 1, f);

  }  
  // get ending time in ms
  gettimeofday(&tend,NULL);

  float timesec = (tend.tv_sec - tbegin.tv_sec)*1000000;
  double totalTime = (tend.tv_usec - tbegin.tv_usec)+timesec; 

  cout << "Sequential write : " << endl ; 
  cout << "Total Time : " << totalTime << "MicroSeconds" <<endl ; 
  env->addResult(totalTime); 

  float throughput = (GB/MB)/(totalTime/1000000);

  //The latency is displayed only if the writing was on the minimal size
  if(size == B){
    float latency = (totalTime/1000) / (5.0*GB); 
    cout << "Latency : "<< latency << "ms" << endl ; 
    //This output format is used in hte script
    cerr << fixed << ";;;;"<< latency << ";" << endl ; 
  }
  cout << "Throughput : " << throughput << "MB/s" <<endl; 
  //This output format is used in the script
  cerr << ";;" << throughput << ";" <<endl; 
  
  free(tmpChar); 
  fclose(f);
  return NULL;
}

void * benchMarkReadRand(void * ptr){

  Environment * env = (Environment*)ptr; 
  const char * type = "r";
  const char * name = env->getFilename();
  FILE * f = fopen(name, type);
  int size = env->getBlockSize();
  int nmemb; 
  int * tabRandom;
  char* tmpChar = (char*) malloc(size*sizeof(char)); 
  int sizeMaxTabRandom=0;
  struct timeval tbegin, tend; 

  // handle B case (memory malloc). Cannot allocate ine GB in the memory for the Random Array
  if(size == B){
    nmemb = 20*MB; 
    tabRandom = (int *) malloc(20*MB*sizeof(int));
    sizeMaxTabRandom = 20*MB; 
  }else {
    nmemb = GB/size; 
    tabRandom = (int *)malloc((GB/size)*sizeof(int));
    sizeMaxTabRandom = GB/size;
  }


  for (int i = 0; i <sizeMaxTabRandom; i++){
    if (size == GB) {
      tabRandom[i] = 0;
    }else if (size == B){
      tabRandom[i] = rand() % ((20*MB)-size);
    }else {
      tabRandom[i] = rand() % (GB-size);
    }
  }
  // get begining random read time in ms
  gettimeofday(&tbegin,NULL);

  for (int i = 0; i<nmemb; i++) {
    fseek(f, tabRandom[i], SEEK_SET);
    fread(tmpChar, 1, size, f);
  }

  // get ending time in ms
  gettimeofday(&tend,NULL);

   float timesec = (tend.tv_sec - tbegin.tv_sec)*1000000;
  double totalTime = (tend.tv_usec - tbegin.tv_usec)+timesec; 

  cout << "Random read : " << endl ; 
  cout << "Total Time : " << totalTime << "MicroSeconds" <<endl ; 
  env->addResult(totalTime);
  
  float throughput =0;
  if (size == B) {
    throughput = 20/(totalTime/1000000);
  }else {
    throughput = (GB/MB)/(totalTime/1000000);
  }

 //The latency is displayed only if the reading was on the minimal size
  if(size == B){
    float latency = (totalTime/1000) / (20*MB); 
    cout << "Latency : "<< latency << "ms" << endl ; 
    //This output format is used in hte script
    cerr << fixed << ";;;;"<< latency << ";" << endl ; 
  }
  cout << "Throughput : " << throughput << "MB/s" <<endl; 
  //This output format is used in hte script
  cerr << ";" << throughput << ";" <<endl; 
  
  free(tmpChar); 
  free(tabRandom);
  fclose(f); 

  return NULL;
}

void * benchMarkWriteRand(void * ptr){

  Environment * env = (Environment*)ptr;  
  const char * type = "w+";
  const char * name = env->getFilename();

  FILE * f = fopen(name, type);
  struct timeval tbegin, tend; 
  int size = env->getBlockSize();
  int nmemb = 0;
  int sizeMaxTabRandom = 0; 
  int * tabRandom;
  char* tmpChar = (char*) malloc(size*sizeof(char));

  for (int i=0; i< size; i++){
    tmpChar[i] = 'b';
  }

  // handle B case (memory malloc)
  if (size == B ){
      nmemb = 10*MB;
      tabRandom = (int*)malloc(sizeof(int)*MB*10);
      sizeMaxTabRandom = 10*MB; 
  }else {
      nmemb = GB/size;
      tabRandom = (int*)malloc(sizeof(int)*(GB/size));
      sizeMaxTabRandom = GB/size; 
  }
  
  for (int i = 0; i <sizeMaxTabRandom; i++){
    if (size == GB) {
      tabRandom[i]=0; 
    }else if (size == B){
      tabRandom[i] = rand() % ((10*MB)-size);
    }else {
      tabRandom[i] = rand()%((int)(GB)-(size));
    }
  }

  // get begining write rand time in ms
  gettimeofday(&tbegin,NULL);

  for (int i =0; i<nmemb; i++) {
    fseek(f, tabRandom[i], SEEK_SET);
    fwrite(tmpChar, size, 1, f);
  }

  // get ending time in ms
  gettimeofday(&tend,NULL);

  float timesec = (tend.tv_sec - tbegin.tv_sec)*1000000;
  double totalTime = (tend.tv_usec - tbegin.tv_usec)+timesec; 

  env->addResult(totalTime);

  cout << "Random write : " << endl ; 
  cout << "Total Time : " << totalTime << "MicroSeconds" <<endl ; 

  float throughput =0;

 //The latency is displayed only if the reading was on the minimal size
  if (size == B) {
    throughput = 10/(totalTime/1000000);
  }else {
    throughput = (GB/MB)/(totalTime/1000000);
  }
  if (size == B){
    float latency = (totalTime/1000) / (10*MB); 
    cout << "Latency : "<< latency << "ms" << endl;
    //This output format is used in hte script
    cerr << fixed << ";;;;"<< latency << ";" << endl ; 
  }
  cout << "Throughput : " << throughput << "MB/s" <<endl; 
  //This output format is used in hte script
  cerr << ";;;" << throughput << ";" <<endl; 

  free(tmpChar); 
  free(tabRandom);
  fclose(f);

  return NULL;
}

